package com.ly.spring;

import com.ly.spring.mapper.IOrderMapper;
import lombok.Data;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.SourceLocation;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.stream.Collectors;

public class AopApplicationContext
                extends MyAnnotationConfigApplicationContext{

    List<Object> aspectBeans = new ArrayList<>();
    // 切点 + 切面信息
    Set<AspectInfo> aspects = new HashSet<>();
    // 需要被代理的对象
    Map<String,Object> proxyBeanMap = new HashMap<>();

    public AopApplicationContext(Class configClass) throws Exception {
        super(configClass);
        // 自定义AOP
        // 1. 筛选 切面对象 @Aspect
        for (Object bean : singtonObjects.values()){
            if (bean.getClass().getAnnotation(Aspect.class) != null  ){
                aspectBeans.add(bean);
            }
        }
        // 2. 分析 切面对象 提取切点
        for (Object aspectBean : aspectBeans){
            Method[] methods = aspectBeans.getClass().getDeclaredMethods();

            for (Method method : methods){
                // 只关注 增强方法  @Before, @After ...
                if (method.getAnnotation(Before.class) != null){
                    // 前置增强方法
                    AspectInfo aspectInfo = new AspectInfo();
                    aspectInfo.adviceMethod = method;
                    aspectInfo.aspectBean = aspectBean;

                    Before before = method.getAnnotation(Before.class);
                    aspectInfo.pointcut = before.value();
                    aspects.add(aspectInfo);
                    aspectInfo.replacePointcut();


                } else if (method.getAnnotation(After.class) != null){
                    // 后增强方法
                    AspectInfo aspectInfo = new AspectInfo();
                    aspectInfo.adviceMethod = method;
                    aspectInfo.aspectBean = aspectBean;

                    After after = method.getAnnotation(After.class);
                    aspectInfo.pointcut = after.value();
                    aspects.add(aspectInfo);

                    aspectInfo.replacePointcut();

                }else {
                    //自行扩展
                }
            }
        }
        System.out.println("-----------------切面对象----------------");
        aspectBeans.forEach(System.out::println);
        System.out.println("-----------------切面信息----------------");
        aspects.forEach(System.out::println);


        // 3. 通过切点判断 IOC 容器中的对象, 哪些对象包含切到的方法
        for (Map.Entry<String, Object> entry : singtonObjects.entrySet()){
            Object bean = entry.getValue();
            String id = entry.getKey();
            a:for (AspectInfo aspect : aspects){
                String pointcut = aspect.pointcut;

                pointcut = pointcut.replaceFirst("execution\\((.+)\\)","$1");
                // 将 .. 和 * 替换为 #  # 作为一个临时的替换字符
                String pointcutRegex = pointcut.replaceAll("\\.{2}|\\*","#");
                // 将 . 替换成 \.  用于匹配 包和类的分割符
                pointcutRegex = pointcutRegex.replaceAll("\\.","\\\\.");
                // 将 # 号 替换为 .*
                pointcutRegex = pointcutRegex.replaceAll("#",".*");
                // 替换 小括号 (
                pointcutRegex = pointcutRegex.replaceAll("\\(","\\\\(");
                // 替换 小括号 )
                pointcutRegex = pointcutRegex.replaceAll("\\)","\\\\)");

                System.out.println("pointcutRegex = " + pointcutRegex);

                for (Method method : bean.getClass().getDeclaredMethods()){
                    String methodStr = method.toString();
                    System.out.println("methodStr = " + methodStr);

                    if (methodStr.matches(pointcutRegex)){
                        System.out.printf("切到的bean %s => %s \n" ,id,method);

                        proxyBeanMap.put(id,bean);
                        break a;
                    }
                }
            }
        }
        System.out.println("-------------- 要增强的bean ------------");
        proxyBeanMap.entrySet().forEach(System.out::println);


        // 4. 包含被切到的方法的对象 => 增强 (JDK or CGLIB)
        // 5. 替换原有业务对象
        for (Map.Entry<String, Object> entry : proxyBeanMap.entrySet()) {
            String id = entry.getKey();
            Object bean = entry.getValue();
            Class<?>[] interfaces = bean.getClass().getInterfaces();
            if(interfaces==null || interfaces.length == 0){
                // 未实现任何接口 => CGLIB 动态代理
            } else {
                // 实现了接口 => JDK 动态代理
                Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                        interfaces,
                        (pb, method, args)->{
                            // 1.遍历切面集合, 过滤出当前切到当前方法的切面
                            List<AspectInfo> beforeAspects = aspects.stream()
                                    .filter(aspectInfo -> {
                                        Before before = aspectInfo.adviceMethod.getAnnotation(Before.class);
                                        if(before == null){
                                            return false;
                                        }
                                        String pointcut = aspectInfo.pointcut.replaceFirst("execution\\((.+)\\)","$1");
                                        // 将 .. 和 * 替换为 #  # 作为一个临时的替换字符
                                        String pointcutRegex = pointcut.replaceAll("\\.{2}|\\*","#");
                                        // 将 . 替换成 \.  用于匹配 包和类的分割符
                                        pointcutRegex = pointcutRegex.replaceAll("\\.","\\\\.");
                                        // 将 # 号 替换为 .*
                                        pointcutRegex = pointcutRegex.replaceAll("#",".*");
                                        // 替换 小括号 (
                                        pointcutRegex = pointcutRegex.replaceAll("\\(","\\\\(");
                                        // 替换 小括号 )
                                        pointcutRegex = pointcutRegex.replaceAll("\\)","\\\\)");
                                        // 判断当前方法是否匹配
                                        return method.toString().matches(pointcutRegex);
                                    })
                                    .collect(Collectors.toList());
                            // 2.调用增强方法
                            if (beforeAspects.isEmpty() == false) {
                                for (AspectInfo aspectInfo : beforeAspects) {
                                    // 执行增强方法
                                    aspectInfo.adviceMethod.invoke(aspectInfo.aspectBean, new JoinPoint() {
                                        @Override
                                        public String toShortString() {
                                            return null;
                                        }

                                        @Override
                                        public String toLongString() {
                                            return null;
                                        }

                                        @Override
                                        public Object getThis() {
                                            return null;
                                        }

                                        @Override
                                        public Object getTarget() {
                                            return null;
                                        }

                                        @Override
                                        public Object[] getArgs() {
                                            return args;
                                        }

                                        @Override
                                        public Signature getSignature() {
                                            return null;
                                        }

                                        @Override
                                        public SourceLocation getSourceLocation() {
                                            return null;
                                        }

                                        @Override
                                        public String getKind() {
                                            return null;
                                        }

                                        @Override
                                        public StaticPart getStaticPart() {
                                            return null;
                                        }
                                    });
                                }
                            }
                            // 执行业务方法
                            try{
                                Object ret = method.invoke(bean, args);
                                // 返回拦截
                                return ret;
                            } catch (Throwable throwable){
                                //  异常拦截
                                throw throwable;
                            } finally {
                                // 后置拦截
                            }
                        });
                // 5. 替换原有业务对象
                singtonObjects.put(id, proxyBean);
            }
        }


    }
    public static void main(String[] args) throws Exception {
        AopApplicationContext cxt = new AopApplicationContext(AopConfig.class);

        IOrderMapper orderMapper = (IOrderMapper) cxt.getBean("orderMapper");
        orderMapper.commit("abc");
    }



}

@Data
class AspectInfo{
    String pointcut;         // 切点
    Method adviceMethod;     // 增强方法
    Object aspectBean;       // 切面对象
    // 替换切点表达式
    void  replacePointcut() throws NoSuchMethodException {
        if (pointcut.matches("(\\w+)\\(\\)")){
            String pointcutMethod = pointcut.replaceFirst("(\\w+)\\(\\)","$1");
            Method method = aspectBean.getClass().getDeclaredMethod(pointcutMethod);

            Pointcut pointcutAnno = method.getAnnotation(Pointcut.class);
            pointcut = pointcutAnno.value();
        }
    }
}
